﻿#include "centralcache.h"
#include "pagecache.h"

// 获取一个非空的span
Span* CentralCache::getOneSpan(SpanList& list, size_t size)
{
	// 1. 查看当前的spanlist中是否有还有未分配对象的span
	Span* it = list.begin();
	while (it != list.end())
	{
		if (it->m_freeList != nullptr)
		{
			return it;
		}
		else
		{
			it = it->m_next;
		}
	}

	// 2. 先把central cache的桶锁解掉，这样如果其他线程释放内存对象回来，不会阻塞
	list.unlock();

	// 3. 走到这里说没有空闲span了，只能找page cache要
	PageCache::getInstance()->lock();
	Span* span = PageCache::getInstance()->newSpan(SizeClass::NumMovePage(size));
	span->m_isUse = true;
	span->m_objSize = size;
	PageCache::getInstance()->unlock();

	// 4. 对获取span进行切分，不需要加锁，因为这会其他线程访问不到这个span
	// 计算span的大块内存的起始地址和大块内存的大小(字节数)
	char* start = (char*)(span->m_pageId << PAGE_SHIFT);
	size_t bytes = span->m_n << PAGE_SHIFT;
	char* end = start + bytes;

	// 5. 把大块内存切成自由链表链接起来
	span->m_freeList = start;
	void* tail = start;
	start += size;
	int i = 1;
	while (start < end)
	{
		++i;
		NextObj(tail) = start;
		tail = NextObj(tail); // tail = start;
		start += size;
	}

	NextObj(tail) = nullptr;

	// 6. 切好span以后，需要把span挂到桶里面去的时候，再加锁
	list.lock();
	list.pushFront(span);

	return span;
}

// 从中心缓存获取一定数量的对象给thread cache
size_t CentralCache::fetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size)
{
	// 1. 计算映射的自由链表桶
	size_t index = SizeClass::Index(size);

	// 2. 开始获取SpanList的span，先加锁
	m_spanLists[index].lock();

	// 3. 获取一个非空的span
	Span* span = getOneSpan(m_spanLists[index], size);
	assert(span);
	assert(span->m_freeList);

	// 4. 从span中获取batchNum个对象，如果不够batchNum个，有多少拿多少
	// 将Span进行划分，划分成batchNum个对象，接到freeList中
	start = span->m_freeList;
	end = start;
	size_t i = 0;
	size_t actualNum = 1;
	while ( i < batchNum - 1 && NextObj(end) != nullptr)
	{
		end = NextObj(end);
		++i;
		++actualNum;
	}
	span->m_freeList = NextObj(end);
	NextObj(end) = nullptr;
	span->m_useCount += actualNum;

	// 5. 解锁
	m_spanLists[index].unlock();

	return actualNum;
}

void CentralCache::releaseListToSpans(void* start, size_t size)
{
	size_t index = SizeClass::Index(size);
	m_spanLists[index].lock();
	while (start)
	{
		void* next = NextObj(start);

		Span* span = PageCache::getInstance()->mapObjectToSpan(start);
		NextObj(start) = span->m_freeList;
		span->m_freeList = start;
		span->m_useCount--;

		// 说明span的切分出去的所有小块内存都回来了
		// 这个span就可以再回收给page cache，pagecache可以再尝试去做前后页的合并
		if (span->m_useCount == 0)
		{
			m_spanLists[index].erase(span);
			span->m_freeList = nullptr;
			span->m_next = nullptr;
			span->m_prev = nullptr;

			// 释放span给page cache时，使用page cache的锁就可以了
			// 这时把桶锁解掉
			m_spanLists[index].unlock();

			PageCache::getInstance()->lock();
			PageCache::getInstance()->releaseSpanToPageCache(span);
			PageCache::getInstance()->unlock();

			m_spanLists[index].lock();
		}

		start = next;
	}

	m_spanLists[index].unlock();
}